﻿using Devonline.AspNetCore;

using Devonline.CloudService.Aliyun;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.Identity;

/// <summary>
/// 登录现在由统一认证提供
/// 在还没有系统账户的情况下, 在认证完成后, 可以通过手机号码和短信验证码登录系统
/// </summary>
public class RealNameAuthenticationService(
    ILogger<RealNameAuthenticationService> logger,
    IDataWithAttachmentService<RealNameAuthUser> dataService,
    IDataService<User> userService,
    IDistributedCache cache,
    IFileService fileService,
    ISmsEndpoint smsEndpoint,
    ISmsService<SmsModel> smsService,
    IIdCardService idCardService,
    IFaceRecognitionService faceRecognitionService,
    PhoneNumberCaptchaService phoneNumberCaptchaService,
    HttpSetting httpSetting)
{
    private readonly ILogger<RealNameAuthenticationService> _logger = logger;
    private readonly IDataWithAttachmentService<RealNameAuthUser> _dataService = dataService;
    private readonly IDataService<User> _userService = userService;
    private readonly IDistributedCache _cache = cache;
    private readonly IFileService _fileService = fileService;
    private readonly ISmsEndpoint _smsEndpoint = smsEndpoint;
    private readonly ISmsService<SmsModel> _smsService = smsService;
    private readonly IIdCardService _idCardService = idCardService;
    private readonly IFaceRecognitionService _faceRecognitionService = faceRecognitionService;
    private readonly HttpSetting _httpSetting = httpSetting;
    private readonly PhoneNumberCaptchaService _phoneNumberCaptchaService = phoneNumberCaptchaService;

    /// <summary>
    /// 获取用户认证信息
    /// 未创建用户(第三方认证登录)的情况下, 调用此方法无法获取用户及实名认证信息
    /// 使用当前用户编号作为实名认证编号查询实名认证信息, 能查到即为存在实名认证信息
    /// </summary>
    /// <returns></returns>
    public async Task<RealNameAuthUser?> GetAsync()
    {
        _logger.LogInformation("{user} 获取个人认证信息", _dataService.UserName);
        return (await _dataService.GetAsync(_dataService.UserId))?.Desensitized();
    }

    /// <summary>
    /// 获取手机验证码
    /// 发送短信验证码
    /// 5 分钟内不会发送重复短信,
    /// 5 分钟内未收到可再次发送, 此时请传递 force 参数, 值为 true
    /// </summary>
    public async Task<RealNameAuthUserViewModel> SendPhoneNumberCaptchaAsync(string phoneNumber)
    {
        var viewModel = GetAuthUserViewModel(await _dataService.GetAsync(_dataService.UserId));
        await _phoneNumberCaptchaService.SendCaptchaAsync(phoneNumber);
        viewModel.AuthPhase = AuthPhase.PhoneNumber;
        viewModel.PhoneNumber = phoneNumber;
        viewModel.IsAuthed = false;
        viewModel.Message = "短信验证码已发送, 请注意查看手机!";
        return viewModel.Desensitized();
    }
    /// <summary>
    /// 验证短信验证码
    /// </summary>
    /// <returns></returns>
    public async Task<RealNameAuthUserViewModel> ValidatePhoneNumberAsync(string code)
    {
        var authUser = await _dataService.GetAsync(_dataService.UserId);
        var viewModel = GetAuthUserViewModel(authUser);
        var captcha = await _phoneNumberCaptchaService.ValidateAsync(code);

        //验证通过时, 用户已登录进行实名认证的情况下:
        //1. 不存在 auth user, 则是系统用户, 先创建了系统账户在做实名认证的手机号码认证的情况, 此时正常创建 auth user, 并使用 user 已有信息填充 auth user, 更新 user 中的手机号码
        //2. 存在 auth user, 则是系统用户, 先创建了系统账户在已经完成手机号码认证, 进行重新认证的情况, 此时更新 auth user, 更新 user 中的手机号码
        var context = DataServiceContext.GetNoSaveChange();
        var user = await _dataService.GetAsync(_dataService.UserId) ?? throw new BadHttpRequestException("当前用户不存在!");

        if (authUser is null)
        {
            //1. 如果不存在 auth user, 则是系统用户, 先创建了系统账户在做实名认证的手机号码认证的情况, 此时正常创建 auth user, 并使用 user 已有信息填充 auth user, 更新 user 中的手机号码
            authUser = new RealNameAuthUser
            {
                Id = user.Id,
                Name = user.Name,
                Alias = user.Alias,
                Type = user.Type,
                Image = user.Image,
                PhoneNumber = captcha.PhoneNumber,
                Captcha = code,
                PhoneNumberValidateFailedCount = captcha.FailedCount,
                PhoneNumberValidateTime = DateTime.Now,
                AuthPhase = AuthPhase.IdCard,
                IsAuthed = false
            };

            await _dataService.AddAsync(authUser, context);
        }
        else
        {
            //2. 如果已存在 auth user, 则是系统用户, 先创建了系统账户在已经完成手机号码认证, 进行重新认证的情况, 此时更新 auth user, 更新 user 中的手机号码
            authUser.Id = user.Id;
            authUser.PhoneNumber = captcha.PhoneNumber;
            authUser.Captcha = code;
            authUser.PhoneNumberValidateFailedCount = captcha.FailedCount;
            authUser.PhoneNumberValidateTime = DateTime.Now;
            if (!authUser.IsAuthed)
            {
                authUser.AuthPhase = AuthPhase.IdCard;
            }

            await _dataService.UpdateAsync(authUser, context);
        }

        await _dataService.SaveChangesAsync();

        viewModel.AuthPhase = authUser.AuthPhase;
        viewModel.IsAuthed = authUser.IsAuthed;
        viewModel.Message = viewModel.IsAuthed ? "短信验证码正确, 手机号码验证已完成!" : "短信验证码正确, 请继续下一步验证!";
        return viewModel.Desensitized();
    }

    /// <summary>
    /// 身份证云接口在线识别验证方法
    /// </summary>
    /// <param name="attachments"></param>
    /// <returns></returns>
    public async Task<IdCardViewModel> GetIdCardInfosAsync(ICollection<Attachment> attachments)
    {
        var authUser = await _dataService.GetAsync(_dataService.UserId);
        _logger.LogInformation($"用户 {_dataService.UserName} 将进行身份证认证识别!");
        if (authUser is null || !authUser.IsAuthed && authUser.AuthPhase < AuthPhase.IdCard)
        {
            throw new BadHttpRequestException("用户尚未进行手机号码认证, 请先进行手机号码认证!");
        }

        GetAvailableAuthUser(authUser, AuthPhase.IdCard);
        var idCard = await _idCardService.GetIdCardInformationAsync(attachments);
        authUser.IdCardValidateTime = DateTime.Now;
        if (idCard is null)
        {
            authUser.IdCardValidateFailedCount++;
            await _dataService.UpdateAsync(authUser);
            throw new BadHttpRequestException("身份证验证失败, 请重新拍照提交!");
        }

        if (idCard.End < DateOnly.FromDateTime(DateTime.Now))
        {
            authUser.IdCardValidateFailedCount++;
            await _dataService.UpdateAsync(authUser);
            throw new BadHttpRequestException("身份证已过期, 请提交有效身份证照片!");
        }

        await _dataService.UpdateAsync(authUser);
        _logger.LogInformation($"用户 {_dataService.UserName} 身份证 {idCard.IdCode} 已成功识别, 待用户确认!");
        return idCard;
    }
    /// <summary>
    /// 身份证验证通过方法, 验证通过后的身份证信息将关联认证用户信息
    /// </summary>
    /// <returns></returns>
    public async Task<RealNameAuthUserViewModel> ValidateIdCardAsync(IdCardViewModel idCardViewModel)
    {
        var authUser = await _dataService.GetAsync(_dataService.UserId);
        _logger.LogInformation($"用户 {_dataService.UserName} 将进行身份证认证!");
        if (authUser is null || !authUser.IsAuthed && authUser.AuthPhase < AuthPhase.IdCard)
        {
            throw new BadHttpRequestException("用户尚未进行手机号码认证, 请先进行手机号码认证!");
        }

        if (string.IsNullOrWhiteSpace(idCardViewModel.IdCode) || idCardViewModel.Attachments?.Count < 2)
        {
            throw new BadHttpRequestException("身份证认证阶段身份证号码不能为空, 且必须包含身份证正反面照片!");
        }

        var viewModel = GetAvailableAuthUser(authUser, AuthPhase.IdCard);

        //验证通过时, 用户已登录进行实名认证的情况下:
        //更新 auth user 和 user 中的身份证信息
        var context = DataServiceContext.GetNoSaveChange();
        var user = await _userService.GetAsync(_dataService.UserId) ?? throw new BadHttpRequestException("当前用户不存在!");

        user.RealNameAuthUserId = authUser.Id;
        user.Name = idCardViewModel.Name;
        await _userService.UpdateAsync(user, context);

        authUser.Name = idCardViewModel.Name;
        authUser.IdCode = idCardViewModel.IdCode;
        authUser.IdCardValidateTime = DateTime.Now;
        authUser.AuthPhase = AuthPhase.IdCard + 1;
        await _idCardService.SaveAsync(idCardViewModel, context);
        await _dataService.UpdateAsync(authUser);

        viewModel.AuthPhase = authUser.AuthPhase;
        viewModel.IsAuthed = false;
        _logger.LogInformation($"用户 {_dataService.UserName} 身份证 {viewModel.IdCode} 认证已完成!");
        return viewModel.Desensitized();
    }

    /// <summary>
    /// 身份证头像和人脸照片比对
    /// </summary>
    /// <param name="attachment"></param>
    /// <returns></returns>
    public async Task<RealNameAuthUserViewModel> FaceCompareAsync(Attachment attachment)
    {
        var authUser = await _dataService.GetAsync(_dataService.UserId);
        _logger.LogInformation($"用户 {_dataService.UserName} 将进行人脸比对!");
        if (authUser is null || string.IsNullOrWhiteSpace(authUser.IdCode) || !authUser.IsAuthed && authUser.AuthPhase < AuthPhase.FaceCompare)
        {
            throw new BadHttpRequestException("用户尚未进行身份证认证, 请先进行身份证认证!");
        }

        var viewModel = GetAvailableAuthUser(authUser, AuthPhase.FaceCompare);
        var imagePath = attachment.Path;
        if (string.IsNullOrWhiteSpace(imagePath))
        {
            throw new BadHttpRequestException("未提交面部照片!");
        }

        imagePath = _fileService.GetAttachmentPath(imagePath);
        if (!File.Exists(imagePath))
        {
            throw new BadHttpRequestException("面部照片文件不存在!");
        }

        var idCard = await _idCardService.GetByIdCodeAsync(authUser.IdCode);
        if (idCard is null || (string.IsNullOrWhiteSpace(idCard.HeadImage) && string.IsNullOrWhiteSpace(idCard.FrontImage)))
        {
            throw new BadHttpRequestException("未完成身份证识别或未从身份证中识别出头像, 请重新进行身份证认证!");
        }

        //如果 HeadImage 不存在就先使用 FrontImage
        var headImage = _fileService.GetAttachmentPath(idCard.HeadImage ?? idCard.FrontImage ?? string.Empty);
        if (!File.Exists(headImage))
        {
            throw new BadHttpRequestException("身份证头像文件不存在!");
        }

        bool result;
        float score;

        try
        {
            (result, score) = await _faceRecognitionService.CompareAsync(headImage, imagePath);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"用户 {_dataService.UserName} 人脸比对失败, " + ex.GetMessage());
            throw new BadHttpRequestException("人脸比对失败, 请重试!");
        }

        authUser.FaceImage = attachment.Path;
        authUser.FaceMatchScore = (float)Math.Round(score, AppSettings.UNIT_FOUR);
        authUser.FaceCompareValidateTime = DateTime.Now;
        _logger.LogInformation($"人脸活体检测结束, 比对得分: {authUser.FaceMatchScore}, 检测结果: {authUser.IsAuthed}");

        if (!result)
        {
            authUser.FaceCompareValidateFailedCount++;
            await _dataService.UpdateAsync(authUser);
            throw new BadHttpRequestException("人脸比对认证未通过, 请重试!");
        }

        await _dataService.AddAttachmentsAsync(authUser, [attachment]);
        authUser.AuthPhase = AuthPhase.FaceCompare + 1;
        await _dataService.UpdateAsync(authUser);

        viewModel.AuthPhase = authUser.AuthPhase;
        viewModel.IsAuthed = authUser.IsAuthed;
        return viewModel.Desensitized();
    }
    /// <summary>
    /// 实名认证完成后提交人脸活体认证视频记录接口
    /// </summary>
    /// <param name="attachments"></param>
    /// <returns></returns>
    public async Task<RealNameAuthUserViewModel> FaceDetectionAsync(ICollection<Attachment> attachments)
    {
        var authUser = await _dataService.GetAsync(_dataService.UserId);
        _logger.LogInformation($"用户 {_dataService.UserName} 将进行人脸活体识别认证!");
        if (authUser is null || !authUser.IsAuthed && authUser.AuthPhase < AuthPhase.FaceDetection)
        {
            throw new BadHttpRequestException("用户尚未进行人脸比对认证, 请先进行人脸比对认证!");
        }

        var viewModel = GetAvailableAuthUser(authUser, AuthPhase.FaceDetection);
        authUser.FaceDetectionValidateTime = DateTime.Now;
        var video = attachments.FirstOrDefault(x => x.ContentType != null && x.ContentType.StartsWith("video"));
        var videoThumbnail = attachments.FirstOrDefault(x => x.ContentType != null && x.ContentType.StartsWith("image"));
        if (video is null || videoThumbnail is null)
        {
            authUser.FaceDetectionValidateFailedCount++;
            await _dataService.UpdateAsync(authUser);
            throw new BadHttpRequestException("实名认证视频或缩略图未提交!");
        }

        //authUser.IsAuthed = authUser.FaceMatchScore >= _appSetting.MatchFaceScore;
        authUser.IsAuthed = true;
        authUser.AuthVideo = video.Path;
        authUser.AuthVideoThumbnail = videoThumbnail.Path;
        authUser.AuthPhase = AuthPhase.FaceDetection + 1;
        await _dataService.UpdateAsync(authUser);

        viewModel.AuthPhase = authUser.AuthPhase;
        viewModel.IsAuthed = authUser.IsAuthed;
        return viewModel.Desensitized();
    }

    /// <summary>
    /// 获取当前用户对应的认证用户
    /// </summary>
    /// <returns></returns>
    private RealNameAuthUserViewModel GetAuthUserViewModel(RealNameAuthUser? authUser)
    {
        _logger.LogInformation("{user} 获取个人认证信息", _dataService.UserName);
        if (authUser is null)
        {
            //此时用户已登录, 但未进行实名认证, 因此不能使用登录用户编号查询到实名认证信息
            return new RealNameAuthUserViewModel
            {
                Id = _dataService.UserId,
                Name = _dataService.UserName
            };
        }

        return new RealNameAuthUserViewModel
        {
            Id = authUser.Id,
            Name = authUser.Name,
            PhoneNumber = authUser.PhoneNumber,
            IdCode = authUser.IdCode,
            AuthPhase = authUser.AuthPhase,
            IsAuthed = authUser.IsAuthed
        };
    }
    /// <summary>
    /// 验证失败次数和失败尝试间隔时间是否超限, 未超限则返回认证对象视图模型
    /// </summary>
    /// <param name="authUser"></param>
    /// <returns></returns>
    private RealNameAuthUserViewModel GetAvailableAuthUser(RealNameAuthUser authUser, AuthPhase authPhase)
    {
        if (!authUser.IsAuthed && authPhase > authUser.AuthPhase)
        {
            throw new BadHttpRequestException($"用户 {authUser.Name} 尚未完成 {authUser.AuthPhase.GetDisplayName()}认证, 此时不能进行 {authPhase.GetDisplayName()} 认证!");
        }

        if ((authPhase == AuthPhase.PhoneNumber && authUser.PhoneNumberValidateFailedCount >= _httpSetting.Execute.MaxRetryCount && authUser.PhoneNumberValidateTime.HasValue && (DateTime.Today <= authUser.PhoneNumberValidateTime.Value.Date))
        || (authPhase == AuthPhase.IdCard && authUser.IdCardValidateFailedCount >= _httpSetting.Execute.MaxRetryCount && authUser.IdCardValidateTime.HasValue && (DateTime.Today <= authUser.IdCardValidateTime.Value.Date))
        || (authPhase == AuthPhase.FaceCompare && authUser.FaceCompareValidateFailedCount >= _httpSetting.Execute.MaxRetryCount && authUser.FaceCompareValidateTime.HasValue && (DateTime.Today <= authUser.FaceCompareValidateTime.Value.Date))
        || (authPhase == AuthPhase.FaceDetection && authUser.FaceDetectionValidateFailedCount >= _httpSetting.Execute.MaxRetryCount && authUser.FaceDetectionValidateTime.HasValue && (DateTime.Today <= authUser.FaceDetectionValidateTime.Value.Date)))
        {
            throw new BadHttpRequestException($"用户 {authUser.Name} 在 {authPhase.GetDisplayName()} 认证中连续失败次数已达上限 {_httpSetting.Execute.MaxRetryCount} 次, 且失败后间隔尝试的时间未超过允许的时间, 此时不能继续认证!");
        }

        if (authPhase == AuthPhase.PhoneNumber && authUser.PhoneNumberValidateFailedCount < _httpSetting.Execute.MaxRetryCount && authUser.PhoneNumberValidateTime.HasValue && (DateTime.Today > authUser.PhoneNumberValidateTime.Value.Date))
        {
            authUser.PhoneNumberValidateFailedCount = 0;
        }

        if (authPhase == AuthPhase.IdCard && authUser.IdCardValidateFailedCount < _httpSetting.Execute.MaxRetryCount && authUser.IdCardValidateTime.HasValue && (DateTime.Today > authUser.IdCardValidateTime.Value.Date))
        {
            authUser.IdCardValidateFailedCount = 0;
        }

        if (authPhase == AuthPhase.FaceCompare && authUser.FaceCompareValidateFailedCount < _httpSetting.Execute.MaxRetryCount && authUser.FaceCompareValidateTime.HasValue && (DateTime.Today > authUser.FaceCompareValidateTime.Value.Date))
        {
            authUser.FaceCompareValidateFailedCount = 0;
        }

        if (authPhase == AuthPhase.FaceDetection && authUser.FaceDetectionValidateFailedCount < _httpSetting.Execute.MaxRetryCount && authUser.FaceDetectionValidateTime.HasValue && (DateTime.Today > authUser.FaceDetectionValidateTime.Value.Date))
        {
            authUser.FaceDetectionValidateFailedCount = 0;
        }

        var viewModel = authUser.CopyTo<RealNameAuthUserViewModel>();
        viewModel.AuthPhase = authPhase;
        return viewModel;
    }
}